﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Dapper;
using Xejen.Database.Entities;
using static Dapper.SqlMapper;

namespace Xejen.Database.Dapper
{
    /// <summary>
    /// 通过Dapper技术进行仓储访问
    /// </summary>
    /// <typeparam name="TDbContext">表示数据库上下文对象<see cref="IDbConnection"/></typeparam>
    /// <typeparam name="TEntity">表示要访问哪张表的实体映射</typeparam>
    /// <typeparam name="TPrimaryKey">表示要访问的表的主键Id的数据类型</typeparam>
    public abstract class DapperRepositoryBase<TDbContext, TEntity, TPrimaryKey> : RepositoryBase<TEntity, TPrimaryKey>, IRepositoryWithDbContext
        where TEntity : class, IEntity<TPrimaryKey>
        where TPrimaryKey : struct
        where TDbContext : IDbConnection
    {
        private bool _disposed;

        /// <inheritdoc cref="IDbContextProvider{TDbContext}.DbContext"/>
        public virtual TDbContext Context => _dbContextProvider.GetDbContext();

        /// <inheritdoc cref="DbConnection" path="/summary"/>
        public virtual DbConnection Connection
        {
            get
            {
                var connection = Context;

                if (connection.State != ConnectionState.Open)
                {
                    connection.Open();
                }

                return connection as DbConnection;
            }
        }

        private readonly IDbContextProvider<TDbContext> _dbContextProvider;

        /// <summary>
        /// <inheritdoc cref="DapperRepositoryBase{TDbContext, TEntity, TPrimaryKey}" path="/summary"/>
        /// </summary>
        /// <param name="dbContextProvider"><inheritdoc cref="IDbContextProvider{TDbContext}" path="/summary"/></param>
        protected DapperRepositoryBase(IDbContextProvider<TDbContext> dbContextProvider)
        {
            _dbContextProvider = dbContextProvider;
        }

        /// <inheritdoc/>
        public override IQueryable<TEntity> GetAll(string sql)
        {
            var entities = Connection.Query<TEntity>(sql).AsQueryable();
            return entities;
        }

        /// <inheritdoc/>
        public override async Task<IQueryable<TEntity>> GetAllAsync(string sql)
        {
            var entities = (await Connection.QueryAsync<TEntity>(sql)).AsQueryable();
            return entities;
        }

        /// <inheritdoc/>
        public override TEntity Single(string sql)
        {
            return Connection.QuerySingle<TEntity>(sql);
        }

        /// <inheritdoc/>
        public override async Task<TEntity> SingleAsync(string sql)
        {
            return await Connection.QuerySingleAsync<TEntity>(sql);
        }

        /// <inheritdoc/>
        public override TEntity SingleOrDefault(string sql)
        {
            return Connection.QuerySingleOrDefault<TEntity>(sql);
        }

        /// <inheritdoc/>
        public override async Task<TEntity> SingleOrDefaultAsync(string sql)
        {
            return await Connection.QuerySingleOrDefaultAsync<TEntity>(sql);
        }

        /// <inheritdoc/>
        public override TEntity FirstOrDefault(string sql)
        {
            return Connection.QueryFirstOrDefault<TEntity>(sql);
        }

        /// <inheritdoc/>
        public override async Task<TEntity> FirstOrDefaultAsync(string sql)
        {
            return await Connection.QueryFirstOrDefaultAsync<TEntity>(sql);
        }

        /// <inheritdoc/>
        public override int Insert(string sql, List<TEntity> entities)
        {
            //var query = "INSERT INTO Users (Name, Age) VALUES (@Name, @Age); SELECT CAST(SCOPE_IDENTITY() as int)";
            return Connection.Execute(sql, entities);
        }

        /// <inheritdoc/>
        public override async Task<int> InsertAsync(string sql, List<TEntity> entities)
        {
            //var query = "INSERT INTO Users (Name, Age) VALUES (@Name, @Age); SELECT CAST(SCOPE_IDENTITY() as int)";
            return await Connection.ExecuteAsync(sql, entities);
        }

        /// <inheritdoc/>
        public override TPrimaryKey InsertAndGetId(string sql, TEntity entity)
        {
            entity = Insert(sql, entity);

            return entity.Id;
        }

        /// <inheritdoc/>
        public override TEntity Update(string sql, TEntity entity)
        {
            //var query = "UPDATE Users SET Name = @Name, Age = @Age WHERE Id = @Id";
            Connection.Execute(sql, entity);
            return entity;
        }

        /// <inheritdoc/>
        public override async Task<TEntity> UpdateAsync(string sql, TEntity entity)
        {
            //var query = "UPDATE Users SET Name = @Name, Age = @Age WHERE Id = @Id";
            await Connection.ExecuteAsync(sql, entity);
            return entity;
        }

        /// <inheritdoc/>
        public override async Task<int> UpdateAsync(string sql, List<TEntity> entities)
        {
            return await Connection.ExecuteAsync(sql, entities);
        }

        /// <inheritdoc/>
        public override void Delete(string sql, TEntity entity)
        {
            Delete(sql, entity.Id);
        }

        /// <inheritdoc/>
        public override void Delete(string sql, TPrimaryKey id)
        {
            Connection.Execute(sql, new { Id = id });
        }

        /// <inheritdoc/>
        public override int Delete(string sql, List<TEntity> entities)
        {
            return Connection.Execute(sql, entities);
        }

        /// <inheritdoc/>
        public override async Task<int> DeleteAsync(string sql, List<TEntity> entities)
        {
            return await Connection.ExecuteAsync(sql, entities);
        }

        /// <inheritdoc/>
        public IDbConnection GetDbContext()
        {
            return Context;
        }

        /// <inheritdoc/>
        protected override void Dispose(bool disposing)
        {
            if (_disposed)
            {
                return;
            }

            if (disposing)
            {
                Context.Dispose();

                _disposed = true;
            }

            base.Dispose(disposing);
        }
    }
}
